
/*
 * Copyright (C) 2008-2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id: Field.java 377 2010-03-11 20:50:18Z archie.cobbs $
 */

package org.dellroad.sidekar;

import org.dellroad.sidekar.schema.model.FieldModel;

/**
 * Defines the structure of one field of an {@link Entity}.
 *
 * @param <T> Java type used to represent this field's value
 */
public abstract class Field<T> extends SchemaEntityElement {

    private final Class<?> type;

    private FieldModel fieldModel;

    /**
     * Constructor.
     *
     * @param name name of this field
     * @param entity the {@link Entity} of which this field is a member
     * @param type raw type of T
     * @throws IllegalArgumentException if name is illegal
     */
    Field(String name, Entity<?> entity, Class<?> type) {
        super(entity, name);
        if (type == null)
            throw new IllegalArgumentException("null type");
        this.type = type;
        this.checkName();
    }

    /**
     * Get the Java type that represents values of this field at runtime.
     */
    public Class<?> getType() {
        return this.type;
    }

    /**
     * Get the {@link Entity} that defined this field.
     *
     * <p>
     * Note that fields are inherited by child entities from their parents.
     * This method returns the original ancestor entity that defined this field.
     * <p>
     *
     * <p>
     * In the case of sub-fields of complex fields (for example, any instance that would be returned from
     * {@link SetField#getElementField}), this method will return {@code null}.
     * </p>
     *
     * @return the entity defining this instance, or {@code null} if this instance is a sub-field of a complex field
     */
    @Override
    public Entity<?> getEntity() {
        return super.getEntity();
    }

    /**
     * Extract the value of this field from the given item.
     *
     * @param item an instance of the {@link Entity} (or any sub-{@link Entity} containing this field)
     * @return the value of this field in the given item
     * @throws DetachedItemException if {@code item} does not exist in the database
     * @throws IllegalArgumentException if {@code item} does not contain this field
     * @throws IllegalStateException if the database containing this field's entity has been closed
     * @throws NullPointerException if the item is null
     * @throws org.dellroad.sidekar.util.SidekarException if an underlying database error occurs
     */
    public abstract T get(Item<?> item);

    /**
     * Get the {@link FieldModel} from which this field was created (if any).
     */
    FieldModel getFieldModel() {
        return this.fieldModel;
    }
    void setFieldModel(FieldModel fieldModel) {
        this.fieldModel = fieldModel;
    }

    /**
     * Visit the given switch according to this instance's type.
     */
    public abstract void visit(FieldSwitch fieldSwitch);

    /**
     * Can this field possibly contain any references to the given entity?
     */
    public abstract boolean canReference(Entity<?> target);

    /**
     * Check field name (used during construction).
     */
    void checkName() {
        if (this.getName() != null) {
            Sidekar.checkValidFieldName(this.getName());
            if (this.getEntity() == null)
                throw new IllegalArgumentException("non-entity fields cannot have names");
        }
    }

    @Override
    public String toString() {
        String entityName = this.getEntity() != null ? this.getEntity().getName() : "<none>";
        return "field " + entityName + "." + this.getName();
    }
}

